#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define HELLO_DEV_NAME      "hello"
#define HELLO_CLASS_NAME    "hello"
#define HELLO_DEV_MAJOR     0
#define HELLO_COUNT         1




static struct cdev *hello_chr_dev = NULL;
static int hello_major = 0;
static int hello_mijor = 0;

static struct class *hello_class;
static struct device *hello_dev;

#define BUFF_SIZE   1024


static int hello_open(struct inode* inode, struct file* filp)
{
    void *tbuff = kzalloc(BUFF_SIZE,GFP_KERNEL);
    if(!tbuff){
        pr_err("[DRIVER ERROR]Alloc Memory failed!\n");
        return -ENOMEM;

    }
    filp->private_data = tbuff;
    return 0;
}

static int hello_release(struct inode* inode, struct file* filp)
{
    void *tbuff = filp->private_data;
    kfree(tbuff);
    return 0;
}

loff_t hello_llseek(struct file* filp,loff_t off,int whence)
{
    loff_t newops;

    switch(whence)
    {
    case 0: //SEEK_SET
        newops = off;
        break;
    case 1: //SEEK_CUR
        newops = filp->f_pos + off;
        break;
    case 2: //SEEK_END
        newops = BUFF_SIZE + off;
        break;
    default:
        return -EINVAL;
    }
    if(newops < 0 || newops > BUFF_SIZE)
        return -EINVAL;
    
    filp->f_pos = newops;
    
    return newops;
}

static ssize_t hello_read(struct file* filp, char __user *buff, size_t count, loff_t* f_pos)
{
    loff_t p = *f_pos;
    void *tbuff = filp->private_data;


    if(p > BUFF_SIZE)
        return -EFAULT;
    if( p + count > BUFF_SIZE)
        return -EFAULT;
    if(copy_to_user(buff,tbuff+p,count))
        return -EFAULT;
    else{
        *f_pos += count;
        return count;
    }
}

static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
    loff_t p = *f_pos;
    void *tbuff = filp->private_data;
    
    if(p > BUFF_SIZE)
        return -EFAULT;
    if( p + count > BUFF_SIZE)
        return -EFAULT;
    if(copy_from_user(tbuff+p,buf,count))
        return -EFAULT;
    else{
        *f_pos += count;
        return count;
    }
}

static long hello_ioctl(struct file* filp, unsigned int cmd,unsigned long arg)
{
    return 0;
}

static const struct file_operations simple_ch_ops = 
{
    .owner = THIS_MODULE,
    .open = hello_open,
    .release = hello_release,
    .llseek = hello_llseek,
    .read = hello_read,
    .write = hello_write,
    .unlocked_ioctl = hello_ioctl,
};

static int __init hello_init(void)
{
    int ret = -1;
    dev_t dev_num;

    printk(KERN_INFO "[DRIVER TEST]Hello World !!\n");
    

    //这里是可以根据是否主设备号来进行申请设备号
    //申请设备号
    if(HELLO_DEV_MAJOR > 0){
        dev_num = MKDEV(HELLO_DEV_MAJOR,0);
        ret = register_chrdev_region(dev_num,HELLO_COUNT,HELLO_DEV_NAME);
    }else{

        ret = alloc_chrdev_region(&dev_num,0,HELLO_COUNT,HELLO_DEV_NAME);
    }

    if(ret < 0){
        pr_err("Get chrdev_region failed!\n");
        goto err;
    }

    hello_major = MAJOR(dev_num);
    hello_mijor = MINOR(dev_num);
    

    //生成设备 另一个的方法是先定义struct cdev，再cdev_init的
    hello_chr_dev = cdev_alloc();
    if(IS_ERR(hello_chr_dev)){
        ret = PTR_ERR(hello_chr_dev);
        pr_err("cdev_alloc failed\n");
        goto out_unreg_chrdev;
    }

    //初始化cdev，添加属性
    hello_chr_dev->owner = THIS_MODULE;
    hello_chr_dev->ops = &simple_ch_ops;
    
    //把字符设备添加进去
    ret = cdev_add(hello_chr_dev,dev_num,1);
    if(ret < 0){
        pr_err("cdev_add failed!\n");
        goto out_unreg_chrdev;
    }
    
    //在/sys/class下创建类,/sys/class/hello
    hello_class = class_create(THIS_MODULE,HELLO_CLASS_NAME);
    if(IS_ERR(hello_class)){
        ret = PTR_ERR(hello_class);
        pr_err("Create class failed.\n");
        goto out_del_cdev;
    }

    //在类/sys/class/hello下创建设备点,/sys/class/hello/hello,
    //同时在/dev/下会出现 /dev/hello
    hello_dev = device_create(hello_class,NULL,dev_num,"%s",HELLO_DEV_NAME);
    if(IS_ERR(hello_dev)){
        ret = PTR_ERR(hello_dev);
        pr_err("Create device failed.\n");
        goto out_unreg_class;
    }
    
    return 0;

out_unreg_class:
    class_destroy(hello_class);

out_del_cdev:
    cdev_del(hello_chr_dev);
    
out_unreg_chrdev:
    unregister_chrdev_region(dev_num,HELLO_COUNT);

err:

    return ret;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "[DRVIER TEST]GoodBye!!\n");
    

    //注销设备
    device_destroy(hello_class,MKDEV(hello_major,hello_mijor));
    //注销类
    class_destroy(hello_class);
    //移除字符设备
    cdev_del(hello_chr_dev);
    //移除设备申请号
    unregister_chrdev_region(MKDEV(hello_major,hello_mijor),HELLO_COUNT);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("Alex_mo");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A hello linux module test");
MODULE_ALIAS("A simple module");
